﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;

namespace Nirvana
{
    public class SpritePool : CSharpSingletion<SpritePool>
    {
        public struct SpritePoolEvent
        {
            public AssetID assetId;
            public Action<Sprite> complete;
            public SpritePoolEvent(AssetID assetid,Action<Sprite> cb)
            {
                this.assetId = assetid;
                this.complete = cb;
            }
        }

        private static Logger logger = LogSystem.GetLogger("SpritePool");
        private Dictionary<AssetID, SpriteCache> spriteCacheAssetIdMap = new Dictionary<AssetID, SpriteCache>();
        private Dictionary<Sprite, SpriteCache> spriteCacheSpriteMap = new Dictionary<Sprite, SpriteCache>();

        private float defaultReleaseAfterFree = 30f;
        private int releaseCount;
        private float curLoadTime;
        private Queue<SpritePoolEvent> spriteEventQueue = new Queue<SpritePoolEvent>();
        private int curLoadingCount;
        private int maxLoadingCount = 4;
        public int MaxLoadingCount
        {
            get { return maxLoadingCount; }
            set { maxLoadingCount = value; }
        }

        public float DefaultReleaseAfterFree
        {
            get
            {
                return defaultReleaseAfterFree;
            }
            set
            {
                defaultReleaseAfterFree = value;
                foreach (var p in spriteCacheAssetIdMap)
                    p.Value.SetReleaseTimeAfterFree(value);
            }
        }

        public Dictionary<AssetID, SpriteCache> GetCacheAssetIDMap()
        {
            return spriteCacheAssetIdMap;
        }
        /// <summary>
        /// 初始化
        /// </summary>
        protected override void Init()
        {
            Scheduler.AddFrameListener(OnUpdate);
        }

        private void OnUpdate()
        {
            this.LoadOnQueue();
            //每秒检测释放未引用的资源
            if (Time.time - curLoadTime >= 1f)
            {
                this.curLoadTime = Time.time;
                this.releaseCount = 0;
                this.spriteCacheAssetIdMap.RemoveAll(UnloadOnTime);
            }
        }
        /// <summary>
        /// 排队下载
        /// </summary>
        private void LoadOnQueue()
        {
            //排队等待
            //int num = this.maxLoadingCount + this.spriteEventQueue.Count / 5;
            //int loadnum = num - this.curLoadingCount;
            //if (loadnum <= 0)
            //{
            //    Debug.LogFormat("Waring: Sprite Pool Loading Busy!!! new Wait moment :{0} ", loadnum);
            //}
            //while (this.spriteEventQueue.Count > 0 && loadnum > 0)

            if (this.spriteEventQueue.Count > 0)
            {
                this.curLoadingCount++;
                SpritePoolEvent _event = this.spriteEventQueue.Dequeue();
                Scheduler.RunCoroutine(this.LoadSpriteASyncByLoadingCount(_event.assetId, _event.complete));
            }
        }

        public void Load(AssetID assetid, Action<Sprite> cb, bool isSync = false)
        {
            if (!assetid.AssetName.EndsWith(".png"))
            {
                assetid.AssetName += ".png";
            }

            if (AssetManager.Simulator.SimulateAssetBundle)
            {
                Scheduler.RunCoroutine(this.LoadSpriteASync(assetid, cb));
                return;
            }
            assetid.AssetName = assetid.AssetName.ToLower();
            if (isSync)
            {
                Scheduler.RunCoroutine(this.LoadSpriteSync(assetid, cb));
                return;
            }

            if (!AssetManager.IsVersionCached(assetid.BundleName))
            {
                Scheduler.RunCoroutine(this.LoadSpriteASync(assetid, cb));
                return;
            }

            SpritePoolEvent _event = new SpritePoolEvent(assetid, cb);
            this.spriteEventQueue.Enqueue(_event);
        }

        public void SetSprite(Image image,Sprite sprite)
        {
            if (image == null || sprite == null) return;
            //SpriteReference sr = ReferenceDict.AddSpriteRefrenceWeak(image, sprite);
            image.sprite = sprite;
            SpriteReferenceHolder  sh = image.GetOrAddComponent<SpriteReferenceHolder>();
            //sh.Set(sr);
            sh.SetSprite(sprite);
        }

        public void SetSprite(SpriteRenderer image, Sprite sprite)
        {
            if (image == null || sprite == null) return;
           // SpriteReference sr = ReferenceDict.AddSpriteRefrenceWeak(image, sprite);
            image.sprite = sprite;
            SpriteReferenceHolder sh = image.GetOrAddComponent<SpriteReferenceHolder>();
            //sh.Set(sr);
            sh.SetSprite(sprite);
        }
        public bool Retain(Sprite sprite)
        {
            if(sprite == null)
            {
                logger.LogError("Try to Retain a null sprite.");
                return false;
            }
            SpriteCache cache;
            if(!this.spriteCacheSpriteMap.TryGetValue(sprite,out cache))
            {
                return false;
            }

            cache.Retain();
            return true;
        }

        public void Free(Sprite sprite ,bool destroy = false)
        {
            if (sprite == null)
            {
                logger.LogError("Try to free a null sprite.");
                return;
            }
            SpriteCache cache;
            if (!this.spriteCacheSpriteMap.TryGetValue(sprite, out cache))
            {
                logger.LogWarning("Try to free an instance {0} not allocated by this pool.", sprite.name);
                return;
            }

            cache.Release();
            if(destroy&& cache.ReferenceCount == 0)
            {
                AssetID assetid = cache.AssetID;
                Sprite _sprite = cache.GetObject();
                if(_sprite != null)
                {
                    AssetManager.UnloadAsseBundle(assetid.BundleName);
                    this.spriteCacheSpriteMap.Remove(_sprite);
                }
                this.spriteCacheAssetIdMap.Remove(assetid);
            }
        }

        public void Clear()
        {
            this.spriteCacheAssetIdMap.Clear();
            this.spriteCacheSpriteMap.Clear();
            this.spriteEventQueue.Clear();
            curLoadingCount = 0;
        }

        public void ClearAllUnused()
        {
            this.spriteCacheAssetIdMap.RemoveAll(Unload);
            this.spriteEventQueue.Clear();
            curLoadingCount = 0;
        }


        private IEnumerator LoadSpriteASyncByLoadingCount(AssetID assetid, Action<Sprite> cb)
        {
            WaitLoadSprite wait = this.WaitLoadSprite(assetid, false);
            if (wait.keepWaiting)
            {
                yield return wait;
            }
            curLoadingCount--;
            if (curLoadingCount < 0)
            {
                Debug.LogError("[SpritePool] loadingCount is occur error " + this.curLoadingCount);
            }
            if (!string.IsNullOrEmpty(wait.Error))
            {
                logger.LogError(wait.Error);
                cb(null);
            }
            else
            {
                cb(wait.LoadedObject);
            }
            yield break;
        }

        private IEnumerator LoadSpriteASync(AssetID assetid, Action<Sprite> cb)
        {
            WaitLoadSprite wait = this.WaitLoadSprite(assetid, false);
            if (wait.keepWaiting)
            {
                yield return wait;
            }
            if (!string.IsNullOrEmpty(wait.Error))
            {
                logger.LogError(wait.Error);
                cb(null);
            }
            else
            {
                cb(wait.LoadedObject);
            }
            yield break;
        }

        private IEnumerator LoadSpriteSync(AssetID assetid, Action<Sprite> cb)
        {
            WaitLoadSprite wait = this.WaitLoadSprite(assetid, true);
            if (wait.keepWaiting)
            {
                yield return wait;
            }
            if (!string.IsNullOrEmpty(wait.Error))
            {
                logger.LogError(wait.Error);
                cb(null);
            }
            else
            {
                cb(wait.LoadedObject);
            }
            yield break;
        }

        private WaitLoadSprite WaitLoadSprite(AssetID assetid, bool isSync)
        {
            SpriteCache cache;
            if (this.spriteCacheAssetIdMap.TryGetValue(assetid, out cache))
            {
                cache.Retain();
                return new WaitLoadSprite(cache);
            }

            cache = new SpriteCache(assetid, this.spriteCacheSpriteMap);
            cache.SetReleaseTimeAfterFree(this.defaultReleaseAfterFree);
            cache.IsSync = isSync;
            cache.Load(assetid);
            cache.Retain();
            this.spriteCacheAssetIdMap.Add(assetid, cache);
            return new WaitLoadSprite(cache);
        }


        private bool UnloadOnTime(AssetID assetid, SpriteCache cache)
        {
            if (!string.IsNullOrEmpty(cache.Error))
            {
                return true;
            }

            if (cache.ReferenceCount > 0)
            {
                return false;
            }
            //1秒时间内，只释放5个资源，避免频繁释放/加载AB
            if (this.releaseCount < 5 && Time.time > cache.LastFreeTime + cache.ReleaseAfterFree)
            {
                this.releaseCount++;
                var obj = cache.GetObject();
                if (obj != null)
                {
                    this.spriteCacheSpriteMap.Remove(obj);
                    AssetManager.UnloadAsseBundle(assetid.BundleName);
                }
                return true;
            }
            return false;
        }

        private bool Unload(AssetID assetid, SpriteCache cache)
        {
            if (!string.IsNullOrEmpty(cache.Error))
            {
                return true;
            }

            if (cache.ReferenceCount > 0)
            {
                return false;
            }

            var obj = cache.GetObject();
            if (obj != null)
            {
                this.spriteCacheSpriteMap.Remove(obj);
                AssetManager.UnloadAsseBundle(assetid.BundleName);
            }
            return true;
        }


    }
}

